use libc::strcpy;
use libc::strlen;
use std::ffi::c_void;
use std::ffi::CString;
use std::mem;
use std::ptr;
const ALLOC_TEST_MAGIC: u32 = 0x72ec82d2;
const MALLOC_PATTERN: u32 = 0xBAADF00D;
const FREE_PATTERN: u32 = 0xDEADBEEF;
#[repr(C)]
pub struct BlockHeader {
    magic_number: u32,
    bytes: usize,
}
static mut ALLOCATED_BYTES: usize = 0;
static mut ALLOCATION_LIMIT: i32 = -1;
pub fn alloc_test_get_header(ptr: *mut c_void) -> *mut BlockHeader {
    let result = unsafe { (ptr as *mut BlockHeader).offset(-1) };
    assert!(unsafe { (*result).magic_number } == ALLOC_TEST_MAGIC);
    result
}
pub fn alloc_test_overwrite(ptr: *mut u8, length: usize, pattern: u32) {
    unsafe {
        for i in 0..length {
            let pattern_seq = (i & 3) as i32;
            let b = ((pattern >> (8 * pattern_seq)) & 0xff) as u8;
            *ptr.add(i) = b;
        }
    }
}
pub fn alloc_test_malloc(bytes: usize) -> *mut c_void {
    unsafe {
        if ALLOCATION_LIMIT == 0 {
            return ptr::null_mut();
        }
        let header = malloc(std::mem::size_of::<BlockHeader>() + bytes) as *mut BlockHeader;
        if header.is_null() {
            return ptr::null_mut();
        }
        (*header).magic_number = ALLOC_TEST_MAGIC;
        (*header).bytes = bytes;
        let ptr = header.add(1) as *mut c_void;
        alloc_test_overwrite(ptr, bytes, MALLOC_PATTERN);
        ALLOCATED_BYTES += bytes;
        if ALLOCATION_LIMIT > 0 {
            ALLOCATION_LIMIT -= 1;
        }
        ptr
    }
}
pub fn alloc_test_free(ptr: *mut c_void) {
    use libc::{c_void, free};
    use std::ptr;
    use std::sync::atomic::{AtomicUsize, Ordering};
    static ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
    if ptr.is_null() {
        return;
    }
    unsafe {
        let header = alloc_test_get_header(ptr);
        let block_size = (*header).bytes;
        assert!(ALLOCATED_BYTES.load(Ordering::SeqCst) >= block_size);
        alloc_test_overwrite(ptr, block_size, FREE_PATTERN);
        (*header).magic_number = 0;
        free(header as *mut c_void);
        ALLOCATED_BYTES.fetch_sub(block_size, Ordering::SeqCst);
    }
}
pub fn alloc_test_realloc(ptr: *mut c_void, bytes: usize) -> *mut c_void {
    unsafe {
        let mut header: *mut BlockHeader;
        let mut new_ptr: *mut c_void;
        let mut bytes_to_copy: usize;
        new_ptr = alloc_test_malloc(bytes);
        if new_ptr.is_null() {
            return ptr::null_mut();
        }
        if !ptr.is_null() {
            header = alloc_test_get_header(ptr);
            bytes_to_copy = (*header).bytes;
            if bytes_to_copy > bytes {
                bytes_to_copy = bytes;
            }
            std::ptr::copy_nonoverlapping(ptr, new_ptr, bytes_to_copy);
            alloc_test_free(ptr);
        }
        new_ptr
    }
}
pub fn alloc_test_calloc(nmemb: usize, bytes: usize) -> *mut c_void {
    let total_bytes = nmemb * bytes;
    let result = unsafe { alloc_test_malloc(total_bytes) };
    if result.is_null() {
        return ptr::null_mut();
    }
    unsafe {
        ptr::write_bytes(result, 0, total_bytes);
    }
    result
}
pub fn alloc_test_strdup(string: &str) -> *mut i8 {
    let c_string = CString::new(string).unwrap();
    let len = unsafe { strlen(c_string.as_ptr()) } + 1;
    let result = unsafe { alloc_test_malloc(len as usize) as *mut i8 };
    if result.is_null() {
        return ptr::null_mut();
    }
    unsafe {
        strcpy(result, c_string.as_ptr());
    }
    result
}
pub fn alloc_test_set_limit(alloc_count: i32) {
    unsafe {
        ALLOCATION_LIMIT = alloc_count;
    }
}
pub fn alloc_test_get_allocated() -> usize {
    unsafe { ALLOCATED_BYTES }
}
